/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdarg.h>
#include <string>

#include "debughelper/debughelper.h"
#include "kmlwriter.h"


using namespace kmlio;


/**
 ****************************************************************************
 * @brief  Constructor
 ****************************************************************************
**/
KmlWriter::KmlWriter() {
    fp          = NULL;
    pretty_flag = true;
    stack.clear();
}

/**
 ****************************************************************************
 * @brief  Destructor
 ****************************************************************************
**/
KmlWriter::~KmlWriter() {
    if (fp != NULL) {
        close();
    }
}

/**
 ****************************************************************************
 * @brief  Open a file and prepare to output KML elements.
 *
 * @param  filename Name of file to be written
 ****************************************************************************
**/
bool KmlWriter::open(const std::string &filename) {
    bool flag = false;

    if (fp != NULL) {
        close();
    }
    stack.clear();
    fp = fopen(filename.c_str(), "wt");
    if (fp != NULL) {
        printf("KmlWriter.open Opened \"%s\" for writing\n", filename.c_str());
        flag = true;
    } else {
        fprintf(stderr, "KmlWriter.open Failed to open \"%s\" for writing\n",
            filename.c_str());
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Outputs the standard UTF-8 XML declaration
 *           \<?xml version="1.0" encoding="UTF-8"?\>.
 ****************************************************************************
**/
bool KmlWriter::addXmlDeclaration() {
    bool flag = false;

    if (fp != NULL) {
        add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Opens the KML XML namespace element.
 ****************************************************************************
**/
bool KmlWriter::addKml() {
    bool flag = false;

    if (fp != NULL) {
        add("<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n");
        stack.push_back("kml");
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Opens the KML \<Document\> element.
 *
 *  The \<Document\> element can contain a \<Style\> and a \<Placemark\> element.
 *
 * @param  name         String declaring the name of the document
 * @param  description  String describing the document
 ****************************************************************************
**/
bool KmlWriter::addDocument(const std::string &name,
                            const std::string &description) {
    bool flag = false;

    if (fp != NULL) {
        add("<Document>\n");
        stack.push_back("Document");
        if (!name.empty()) {
            add("<name>%s</name>\n", name.c_str());
        }
        if (!description.empty()) {
            add("<description>%s</description>\n", description.c_str());
        }
        flag = true;
    }

    return flag;
}


/**
 ****************************************************************************
 * @brief  Opens the KML \<Placemark\> element.
 *
 *  The \<Placemark\> element can contain a \<LineString\> element.
 *
 * @param  name         String declaring the name of the placemark
 * @param  description  String describing the placemark
 * @param  styleUrl     String containing the placemark style URL
 * @param  visibility   The placemark will be visible if this value is one
 ****************************************************************************
**/
bool KmlWriter::addPlacemark(const std::string &name,
                             const std::string &description,
                             const std::string &styleUrl,
                             int visibility) {
    bool flag = false;

    if (fp != NULL) {
        add("<Placemark>\n");
        stack.push_back("Placemark");
        if (!name.empty()) {
            add("<name>%s</name>\n", name.c_str());
        }
        if (!description.empty()) {
            add("<description>%s</description>\n", description.c_str());
        }
        if (!styleUrl.empty()) {
            add("<styleUrl>%s</styleUrl>\n", styleUrl.c_str());
        }
        add("<visibility>%d</visibility>\n", visibility);
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Opens the KML \<LineString\> element.
 *
 *  The \<LineString\> element usually contains a \<coordinates\> element.
 *
 * @param  altitudeMode String containing the line string altitude mode
 * @param  tessellate   The line string will be tessellated if this value is one
 ****************************************************************************
**/
bool KmlWriter::addLineString(const std::string &altitudeMode, int tessellate) {
    bool flag = false;

    if (fp != NULL) {
        add("<LineString>\n");
        stack.push_back("LineString");
        add("<altitudeMode>%s</altitudeMode>\n", altitudeMode.c_str());
        add("<tessellate>%d</tessellate>\n", tessellate);
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Opens the KML \<Style\> element.
 *
 *  The \<Style\> element usually will contain a \<LineStyle\> and a \<PolyStyle\> element.
 *
 * @param  id           String containing the style ID attribute
 ****************************************************************************
**/
bool KmlWriter::addStyle(const std::string &id) {
    bool flag = false;

    if (fp != NULL) {
        add("<Style id=\"%s\">\n", id.c_str());
        stack.push_back("Style");
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Outputs the KML \<LineStyle\> element.
 *
 * @param  color        String containing the line style color
 * @param  width        Floating point line style width
 ****************************************************************************
**/
bool KmlWriter::addLineStyle(const std::string &color, double width) {
    bool flag = false;

    if (fp != NULL) {
        add("<LineStyle>\n");
        stack.push_back("LineStyle");
        add("<color>%s</color>\n", color.c_str());
        add("<width>%.1f</width>\n", width);
        flag = close1();
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Outputs the KML \<PolyStyle\> element.
 *
 * @param  color        String containing the poly style color
 ****************************************************************************
**/
bool KmlWriter::addPolyStyle(const std::string &color) {
    bool flag = false;

    if (fp != NULL) {
        add("<PolyStyle>\n");
        stack.push_back("PolyStyle");
        add("<color>%s</color>\n", color.c_str());
        flag = close1();
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Opens the KML \<coordinates\> element.
 *
 *  The \<coordinates\> element usually contains multiple coordinate text nodes.
 ****************************************************************************
**/
bool KmlWriter::addCoordinates() {
    bool flag = false;

    if (fp != NULL) {
        add("<coordinates>\n");
        stack.push_back("coordinates");
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Outputs a KML coordinate text node.
 *
 * @param  latitude_degN   Latitude in decimal degrees North of the equator
 * @param  longitude_degE  Longitude in decimal degrees East of the Prime Meridian
 * @param  altitude_m      Height above mean sea level in meters
 ****************************************************************************
**/
bool KmlWriter::addCoordinate(double latitude_degN,
                              double longitude_degE,
                              double altitude_m) {
    bool flag = false;

    if (fp != NULL) {
        if (altitude_m == 0.0) {
            add("%.12f,%.12f\n", longitude_degE, latitude_degN);
        } else {
            add("%.12f,%.12f,%.2f\n",
                longitude_degE, latitude_degN, altitude_m);
        }
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Closes the most recently opened XML element.
 ****************************************************************************
**/
bool KmlWriter::close1() {
    bool flag = false;

    if (fp != NULL) {
        if (!stack.empty()) {
            std::string element = stack.back();
            stack.pop_back();
            add("</%s>\n", element.c_str());
            flag = true;
        } else {
            fprintf(stderr, "KmlWrite.close1 No elements on stack\n");
        }
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Closes all open XML elements, starting with the most
 *           recently opened element and then closes the output file.
 ****************************************************************************
**/
bool KmlWriter::close() {
    bool flag = false;

    if (fp != NULL) {
        while (!stack.empty()) {
            close1();
        }
        fclose(fp);
        fp = NULL;
        flag = true;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Set the pretty flag.
 *
 *  A true value will cause the XML elements to be indented in the output
 *   (this is the default).
 *  A false value will not indent the elements and reduce the file size.
 *
 * @param  flag  True causes the XML elements to be indented in the output
 ****************************************************************************
**/
void KmlWriter::setPretty(bool flag) {
    pretty_flag = flag;
}

/**
 ****************************************************************************
 * @brief  Write formatted output to the file.
 *
 * @param  format  Format-control string
 * @param  ...     Optional arguments
 ****************************************************************************
**/
void KmlWriter::add(const char* format, ...) {
    static const char *space40 = "                                        ";
    if (pretty_flag) {
        int i = static_cast<int> (stack.size()) * 2;
        if (i > 40) {
            i = 40;
        }
        if (i > 0) {
            fprintf(fp, &space40[40 - i]);
        }
    }
    va_list arg;
    va_start(arg, format);
    vfprintf(fp, format, arg);
    va_end(arg);
}

/**
 ****************************************************************************
 * @brief  Self test method.
 ****************************************************************************
**/
bool KmlWriter::unitTest() {
    bool flag = false;

    kmlio::KmlWriter kmlWriter;
    kmlWriter.open("example.kml");
    kmlWriter.addXmlDeclaration();
    kmlWriter.addKml();
    kmlWriter.addDocument("Example", "Example line string with one point");
    kmlWriter.addStyle("transPurpleLineGreenPoly");
    kmlWriter.addLineStyle("7fff00ff", 4);
    kmlWriter.addPolyStyle("7f00ff00");
    kmlWriter.close1();  // style
    kmlWriter.addPlacemark("", "", "#transPurpleLineGreenPoly");
    kmlWriter.addLineString("clampToGround");
    kmlWriter.addCoordinates();
    kmlWriter.addCoordinate(37.2137, -80.4376);
    kmlWriter.addCoordinate(37.2136, -80.4376);
    kmlWriter.addCoordinate(37.2136, -80.4377);
    kmlWriter.addCoordinate(37.2137, -80.4376);
    flag = kmlWriter.close();

    printf("KmlWriter.unitTest %s\n", (flag) ? "Passed" : "Failed");

    return flag;
}
